/*
 * Copyright 2010 J Patrick Davenport
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package v4view.web;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.EqualsBuilder;

import v4view.core.AttributesNotSupportedException;

/**
 * Represents an &lt;a&gt; tag.
 * @author J Patrick Davenport
 *
 */
public class A extends ChildbearingPageElement implements IInlineElement, ICoreAttributes, ILanguageAttributes<A>, IMouseEvents<A>, IKeyboardEvents<A> {
	private static final String	TAG					= "a";
	private static final String	HREF_ATTRIBUTE		= "href";
	private static final String	HREF_LANG_ATTRIBUTE	= "hreflang";
	private static final String	NAME_ATTRIBUTE		= "name";
	private static final String	REL_ATTRIBUTE		= "rel";
	private static final String	REV_ATTRIBUTE		= "rev";
	private static final String	SHAPE_ATTRIBUTE		= "shape";
	private HrefCreator			creator;
	private Set<Parameter>		params;
	private boolean				appendAppContext;

	{
		this.setTag(TAG);
		this.setSupportsAttributes(true);
		this.creator = new HrefCreator();
	}

	public A() {
	}

	/**
	 * Constructs an A tag with an href and a text element between the open and close tags.
	 * @param _href the href for the resource you wish to link to.
	 * @param _value the text you want to display as a link.
	 */
	public A(final String _href, final String _value) {
		this.setHref(_href);
		this.addPageElement(new Content(_value));
	}

	@Override
	public void render() throws IOException {
		String url = this.getHref();
		if (this.appendAppContext && url != null) {
			final HttpServletRequest req = this.getRequest();
			if (req != null) {
				url = req.getContextPath() + url;
				this.setHref(url);
			}
		}
		super.render();
	}

	/**
	 * Adds a parameter to the existing href.
	 * @param _name parameter name
	 * @param _value the value you want to pass
	 * @return the link you called addParameter on.
	 */
	public A addParameter(final String _name, final String _value) {
		final Parameter param = new Parameter(_name, _value);
		this.checkParams().remove(param);
		this.checkParams().add(param);
		this.creator.generateAndUpdateHref();
		return this;
	}

	public A removeParameter(final String _byName) {
		final Parameter p = new Parameter(_byName, null);
		this.checkParams().remove(p);
		this.creator.removeParameter(p);
		return this;
	}

	public A setShape(final String _value) {
		return this.setAttribute(SHAPE_ATTRIBUTE, _value);
	}

	public String getShape() {
		return this.getAttribute(SHAPE_ATTRIBUTE).getValue();
	}

	public A setRev(final String _value) {
		return this.setAttribute(REV_ATTRIBUTE, _value);
	}

	public String getRev() {
		return this.getAttribute(REV_ATTRIBUTE).getValue();
	}

	public A setRel(final String _value) {
		return this.setAttribute(REL_ATTRIBUTE, _value);
	}

	public String getRel() {
		return this.getAttribute(REL_ATTRIBUTE).getValue();
	}

	public A setName(final String _value) {
		return this.setAttribute(NAME_ATTRIBUTE, _value);
	}

	public String getName() {
		return this.getAttribute(NAME_ATTRIBUTE).getValue();
	}

	public A setHrefLang(final String _value) {
		return this.setAttribute(HREF_LANG_ATTRIBUTE, _value);
	}

	public String getHrefLang() {
		return this.getAttribute(HREF_LANG_ATTRIBUTE).getValue();
	}

	public A setHref(final String _value) {
		this.setAttribute(HREF_ATTRIBUTE, _value).creator.generateAndUpdateHref();
		return this;
	}

	public String getHref() {
		return this.getAttributeValue(HREF_ATTRIBUTE);
	}

	@Override
	public String getCssClass() {
		return this.getAttributeValue(CSS_CLASS_KEY);
	}

	@Override
	public String getId() {
		return this.getAttributeValue(ID_KEY);
	}

	@Override
	public String getStyle() {
		return this.getAttributeValue(STYLE_KEY);
	}

	@Override
	public String getTitle() {
		return this.getAttributeValue(TITLE_KEY);
	}

	@Override
	public A setCssClass(final String _value) {
		return this.setAttribute(CSS_CLASS_KEY, _value);
	}

	@Override
	public A setId(final String _value) {
		return this.setAttribute(ID_KEY, _value);
	}

	@Override
	public A setStyle(final String _value) {
		return this.setAttribute(STYLE_KEY, _value);
	}

	@Override
	public A setTitle(final String _value) {
		return this.setAttribute(TITLE_KEY, _value);
	}

	@Override
	public A setOnKeyDown(final String _scriptlet) {
		return this.setAttribute(KEY_DOWN_EVENT_KEY, _scriptlet);
	}

	@Override
	public A setOnKeyPress(final String _scriptlet) {
		return this.setAttribute(KEY_PRESS_EVENT_KEY, _scriptlet);
	}

	@Override
	public A setOnKeyUp(final String _scriptlet) {
		return this.setAttribute(KEY_UP_EVENT_KEY, _scriptlet);
	}

	@Override
	public String getOnKeyDown() {
		return this.getAttributeValue(KEY_DOWN_EVENT_KEY);
	}

	@Override
	public String getOnKeyPress() {
		return this.getAttributeValue(KEY_PRESS_EVENT_KEY);
	}

	@Override
	public String getOnKeyUp() {
		return this.getAttributeValue(KEY_UP_EVENT_KEY);
	}

	@Override
	public A setDir(final String _value) {
		return this.setAttribute(DIR_KEY, _value);
	}

	@Override
	public A setLang(final String _lang) {
		return this.setAttribute(LANG_KEY, _lang);
	}

	@Override
	public A setXmlLang(final String _xmlLang) {
		return this.setAttribute(XML_LANG_KEY, _xmlLang);
	}

	@Override
	public String getDir() {
		return this.getAttributeValue(DIR_KEY);
	}

	@Override
	public String getLang() {
		return this.getAttributeValue(LANG_KEY);
	}

	@Override
	public String getXmlLang() {
		return this.getAttributeValue(XML_LANG_KEY);
	}

	@Override
	public A setOnClick(final String _scriptlet) {
		return this.setAttribute(ON_MOUSECLICK_EVENT_KEY, _scriptlet);
	}

	@Override
	public A setOnDblClick(final String _scriptlet) {
		return this.setAttribute(ON_MOUSEDBLCLICK_EVENT_KEY, _scriptlet);
	}

	@Override
	public A setOnMouseDown(final String _scriptlet) {
		return this.setAttribute(ON_MOUSEDOWN_EVENT_KEY, _scriptlet);
	}

	@Override
	public A setOnMouseMove(final String _scriptlet) {
		return this.setAttribute(ON_MOUSEMOVE_EVENT_KEY, _scriptlet);
	}

	@Override
	public A setMouseOut(final String _scriptlet) {
		return this.setAttribute(ON_MOUSEOUT_EVENT_KEY, _scriptlet);
	}

	@Override
	public A setMouseOver(final String _scriptlet) {
		return this.setAttribute(ON_MOUSEOVER_EVENT_KEY, _scriptlet);
	}

	@Override
	public A setMouseUp(final String _scriptlet) {
		return this.setAttribute(ON_MOUSEUP_EVENT_KEY, _scriptlet);
	}

	@Override
	public String getOnClick() {
		return this.getAttributeValue(ON_MOUSECLICK_EVENT_KEY);
	}

	@Override
	public String getOnDblClick() {
		return this.getAttributeValue(ON_MOUSEDBLCLICK_EVENT_KEY);
	}

	@Override
	public String getOnMouseDown() {
		return this.getAttributeValue(ON_MOUSEDOWN_EVENT_KEY);
	}

	@Override
	public String getOnMouseMove() {
		return this.getAttributeValue(ON_MOUSEMOVE_EVENT_KEY);
	}

	@Override
	public String getMouseOut() {
		return this.getAttributeValue(ON_MOUSEOUT_EVENT_KEY);
	}

	@Override
	public String getMouseOver() {
		return this.getAttributeValue(ON_MOUSEOVER_EVENT_KEY);
	}

	@Override
	public String getMouseUp() {
		return this.getAttributeValue(ON_MOUSEUP_EVENT_KEY);
	}

	@Override
	public A addPageElement(final PageElement _element) {
		super.addPageElement(_element);
		return this;
	}

	@Override
	public A setAttribute(final String _name, final String _value) throws IllegalArgumentException, AttributesNotSupportedException {
		super.setAttribute(_name, _value);
		return this;
	}

	class Parameter {
		public String	name, value;

		public Parameter(final String _name, final String _value) {
			this.name = _name;
			this.value = _value;
		}

		@Override
		public boolean equals(final Object _obj) {
			if (_obj == null || !(_obj instanceof Parameter)) { return false; }
			final Parameter in = (Parameter) _obj;
			return new EqualsBuilder().append(this.name, in.name).isEquals();
		}

		@Override
		public int hashCode() {
			return this.name == null ? 0 : this.name.hashCode();
		}

		@Override
		public String toString() {
			return this.name + "=" + this.value;
		}
	}

	/**
	 * Creates a simple interface for handling the complexities of solving URLs.
	 * @author J Patrick Davenport
	 *
	 */
	private class HrefCreator {
		private static final String	PARAM_INDICATOR	= "\\?";

		@SuppressWarnings("unchecked")
		public void removeParameter(final Parameter p) {
			final Object[] baseURLAndParamSet = this.getBaseURLAndParamSet();
			if (baseURLAndParamSet == null || baseURLAndParamSet.length == 1) { return; }
			final Set<Parameter> set = (Set<Parameter>) baseURLAndParamSet[1];
			set.remove(p);
			final String renderURL = this.renderURL((String) baseURLAndParamSet[0], set);
			A.this.setHref(renderURL);
			this.generateAndUpdateHref();
		}

		@SuppressWarnings("unchecked")
		public void generateAndUpdateHref() {
			final Object[] urlParts = this.getBaseURLAndParamSet();
			if (urlParts == null) { return; }
			Set<Parameter> currentParams = (Set<Parameter>) urlParts[1];

			/*
			 * So here's a trick, while a bit expensive,
			 * we can interate through the parent's params and
			 * add them to the generated params. This will kick out
			 * existing params, with old values, and update the slot.
			 * Then we iterate over the list again to generate the
			 * string representation of the url.
			 */
			final Set<Parameter> parentParams = A.this.checkParams();
			if (currentParams == null) {
				currentParams = new HashSet<A.Parameter>();
			}
			for (final Parameter p : parentParams) {
				currentParams.remove(p); // Stupid Java Set api. Add should be able to evict on add.
				currentParams.add(p);
			}

			A.this.setAttribute(A.HREF_ATTRIBUTE, this.renderURL((String) urlParts[0], currentParams), false);
		}

		/**
		 * 
		 * @return an Array where index 0 is the String of the base url. Index 1 is a Set of params.
		 * I used an array rather than create a new object just for this.
		 */
		private Object[] getBaseURLAndParamSet() {
			final String href = A.this.getHref();
			if (StringUtils.isEmpty(href)) { return null; }

			final Object[] result = new Object[2];
			final String[] urlParts = href.split(PARAM_INDICATOR);
			if (urlParts != null) {
				result[0] = urlParts[0];
				if (urlParts.length == 2) {
					final Set<Parameter> existingParams = this.createParams(urlParts[1]);
					result[1] = existingParams;
				}
			}
			return result;
		}

		/**
		 * Parses the query portion of the url and creates the parameters.
		 * @param _query everyth to the right of the ?
		 * @return a set of params, possible length of zero, for the query.
		 */
		private Set<A.Parameter> createParams(final String _query) {
			final HashSet<A.Parameter> generatedParams = new HashSet<A.Parameter>();
			if (_query != null) { // Could be possible that the query didn't have any params.
				final String[] nameValuePairs = _query.split("&");
				for (final String pair : nameValuePairs) {
					final int equalsIndex = pair.indexOf("=");
					final String name = pair.substring(0, equalsIndex);
					final String value = equalsIndex < pair.length() ? pair.substring(equalsIndex + 1) : "";
					final Parameter param = new Parameter(name, value);
					generatedParams.add(param);
				}
			}

			return generatedParams;
		}

		private String renderURL(final String _baseUrl, final Set<Parameter> _params) {
			final StringBuilder sb = new StringBuilder();
			sb.append(_baseUrl);
			if (CollectionUtils.isNotEmpty(_params)) {
				sb.append("?");
				for (final Parameter p : _params) {
					sb.append(StringEscapeUtils.escapeHtml(p.name))
								.append("=")
								.append(StringEscapeUtils.escapeHtml(p.value))
								.append("&");
				}
				sb.setLength(sb.length() - 1);
			}

			return sb.toString();
		}
	}

	private Set<Parameter> checkParams() {
		if (this.params == null) {
			this.params = new HashSet<A.Parameter>();
		}

		return this.params;
	}

	public boolean isAppendAppContext() {
		return this.appendAppContext;
	}

	public A setAppendAppContext(final boolean _appendAppContext) {
		this.appendAppContext = _appendAppContext;
		this.setHref(this.getHref());
		return this;
	}

}
